//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using LargoCommon.Music;
namespace LargoCommon.Midi
{
///
/// Midi Tone Collection.
///
[Serializable]
public sealed class MidiTones : IMidiTones
{
#region Fields
///
/// Musical metric.
///
[NonSerialized]
private MusicalMetric metric;
///
/// Last Bar Number.
///
private int lastBarNumber;
///
/// Current Delta Time.
///
private long currentStartTime;
///
/// Current Note.
///
private byte currentNoteNumber;
///
/// The list.
///
private List list;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The given midi track.
public MidiTones(IMidiTrack givenMidiTrack) {
Contract.Requires(givenMidiTrack != null);
this.Metric = givenMidiTrack.Metric;
this.List = new List();
this.MidiTrack = givenMidiTrack;
this.Name = givenMidiTrack.Name;
this.BarDivision = givenMidiTrack.BarDivision;
this.InstrumentNumber = givenMidiTrack.InstrumentNumber;
this.Channel = (byte)givenMidiTrack.Channel;
this.Octave = givenMidiTrack.Octave;
this.BandType = givenMidiTrack.BandType;
this.Tempo = givenMidiTrack.Tempo;
this.IsRhythmical = givenMidiTrack.IsRhythmical;
if (this.BarDivision != 0) {
this.ReadMidiTones(givenMidiTrack);
}
}
///
/// Initializes a new instance of the class.
///
/// The given midi track.
/// The given area.
public MidiTones(IMidiTrack givenMidiTrack, MusicalSection givenArea)
: this(givenMidiTrack) {
var tones = (from tone in this.List
where tone.BarNumberFrom >= givenArea.BarFrom && tone.BarNumberFrom <= givenArea.BarTo //// && tone.StartTime > 18432 && tone.StartTime < 20000
orderby tone.BarNumberFrom, tone.StartTime ////, tone.Duration
select tone).ToList();
this.SetTones(tones);
this.SetFirstBarNumber(givenArea.BarFrom);
}
///
/// Initializes a new instance of the class.
///
/// The list of tones.
[UsedImplicitly]
public MidiTones(IList list) {
Contract.Requires(list != null);
this.Metric = new MusicalMetric(1, 0);
this.List = list.ToList();
}
///
/// Prevents a default instance of the class from being created.
///
private MidiTones() {
this.Metric = new MusicalMetric(1, 0);
this.List = new List();
}
///
/// Initializes a new instance of the MidiTones class.
///
/// The prototype.
/// Given list.
private MidiTones(MidiTones prototype, IEnumerable givenList) {
Contract.Requires(prototype != null);
this.MidiTrack = prototype.MidiTrack;
this.Name = prototype.Name;
this.Number = prototype.Number;
this.BarDivision = prototype.BarDivision;
this.InstrumentNumber = prototype.InstrumentNumber;
this.Channel = prototype.Channel;
this.Octave = prototype.Octave;
this.BandType = prototype.BandType;
this.IsRhythmical = prototype.IsRhythmical;
this.Tempo = prototype.Tempo;
this.Metric = prototype.Metric;
this.List = givenList.ToList();
}
#endregion
#region Public properties
///
/// Gets or sets the metric.
///
///
/// The metric.
///
/// Metric is null.
/// Metric cannot be set null.;value
public MusicalMetric Metric {
get {
Contract.Ensures(Contract.Result() != null);
if (this.metric == null) {
throw new InvalidOperationException("Metric is null.");
}
return this.metric;
}
set => this.metric = value ?? throw new ArgumentException("Metric cannot be set null.", nameof(value));
}
///
/// Gets the list.
///
/// Property description.
public IList List {
get => this.list;
private set => this.list = (List)value;
}
///
/// Gets name of the collection.
///
/// Property description.
public string Name { get; }
///
/// Gets the FirstBarNumber (because of division to blocks).
///
/// Property description.
public int FirstBarNumber { get; private set; }
///
/// Gets BarDivision.
///
/// Property description.
public int BarDivision { get; }
///
/// Gets instrument.
///
/// Property description.
public MusicalOctave Octave { get; }
///
/// Gets instrument.
///
/// Property description.
public MusicalBand BandType { get; }
///
/// Gets a value indicating whether Is Rhythmical.
///
///
/// Property description.
///
public bool IsRhythmical { get; }
/// Gets musical tempo.
/// Property description.
private int Tempo { get; }
///
/// Gets BarDivision.
///
/// Property description.
private int Number { get; }
///
/// Gets instrument.
///
/// Property description.
private byte InstrumentNumber { get; }
///
/// Gets Channel.
///
/// Property description.
private byte Channel { get; }
///
/// Gets Tones Having Duration.
///
/// Property description.
private MidiTones TonesHavingDuration {
get {
IList listTones = this.List.Where(t => (t.Duration > 0)).OrderBy(t => t.StartTime).ToList();
var c = new MidiTones(this, listTones);
return c;
}
}
/// Gets or sets Collection of MIDI event added to this track.
/// Property description.
private Collection OtherEventList { get; set; }
///
/// Gets the MidiTrack.
///
/// Property description.
private IMidiTrack MidiTrack { get; }
#endregion
///
/// Checks the tones - because of problem with sounding over end of tone
///
/// Returns value.
public bool CheckTones() {
//// var status = true;
var tones = (from tone in this.List
where tone.Loudness > 0 && tone.BarNumberTo - tone.BarNumberFrom > 10
orderby tone.BarNumberFrom, tone.StartTime
select tone).ToList();
if (tones.Count <= 0) {
return true;
}
//// status = false;
//// var tone1 = list.FirstOrDefault();
////201508 MessageBox.Show(string.Format("Tone too long! \n {0} ", tone1));
//// foreach (var tone in list) { MessageBox.Show(string.Format("Tone too long! \n {0} ", tone)); }
return false;
}
///
/// Check Rests.
///
public void CheckRests() {
var tones = (from tone in this.List
where tone.Loudness == 0 && tone.BarNumberTo - tone.BarNumberFrom > 10
orderby tone.BarNumberFrom, tone.StartTime
select tone).ToList();
if (tones.Count <= 0) {
}
////201508 foreach (var tone in list) { MessageBox.Show(string.Format("Rest too long! \n {0} ", tone)); }
}
///
/// Sets the tones.
///
/// The given list.
public void SetTones(IEnumerable givenList) {
Contract.Requires(givenList != null);
this.list.Clear();
this.list.AddRange(givenList);
}
///
/// Sets the first bar number.
///
/// The bar number.
public void SetFirstBarNumber(int barNumber) {
this.FirstBarNumber = barNumber;
this.list.ForEach(mtone => {
checked {
mtone.BarNumberFrom -= barNumber - 1;
mtone.BarNumberTo -= barNumber - 1;
}
});
}
#region MidiTones
///
/// Completes all tones.
///
/// The midi track.
/// The tones.
/// All tones.
private static void CompleteAllTones(IMidiTrack midiTrack, MidiTones tones, MidiTones allTones) {
Contract.Requires(tones != null);
Contract.Requires(midiTrack != null);
Contract.Requires(allTones != null);
long lastTime = 0;
tones.list.ForEach(rmt => {
if (rmt.StartTime > lastTime) {
var mt = new MidiTone(lastTime, 0, 0, rmt.InstrumentNumber, rmt.Channel) {
Duration = rmt.StartTime - lastTime,
BarNumberFrom = (int)Math.Floor((double)lastTime / midiTrack.BarDivision) + 1,
BarNumberTo = (int)Math.Floor((double)rmt.StartTime / midiTrack.BarDivision) + 1
};
allTones.List.Add(mt);
}
lastTime = rmt.StartTime + rmt.Duration;
});
}
///
/// Read Midi Tones.
///
/// Midi Track.
[ContractVerification(false)]
private void ReadMidiTones(IMidiTrack midiTrack) { //// cyclomatic complexity 10:11
Contract.Requires(midiTrack != null);
if (midiTrack.BarDivision == 0) {
throw new InvalidOperationException("Bar division is zero.");
}
var tones = new MidiTones();
this.lastBarNumber = -1;
//// !!!!!! 2013/11 midiTrack.Events.RecomputeAbsoluteTimes();
//// this.eventList.SortByStartTime();
var allTones = new MidiTones();
this.OtherEventList = new Collection();
this.currentStartTime = 0;
this.currentNoteNumber = 0;
const bool fullLength = true;
this.PrepareMidiTones(midiTrack, tones, allTones, fullLength);
allTones.AddRange(tones);
//// when note off events are missing
allTones.CompleteDurations(fullLength, midiTrack.BarDivision);
midiTrack.Events.RecomputeDeltaTimes();
//// Where(t => (t.Velocity>0 && t.Duration==0)
tones = allTones.TonesHavingDuration;
CompleteAllTones(midiTrack, tones, allTones);
//// 2014/12 Time optimization
allTones.list.RemoveAll(mtone => mtone.Duration <= 0);
var orderedTones = allTones.List.OrderBy(t => t.StartTime);
//// var midiTones = allTones.List.Where(t => (t.Duration > 0)).OrderBy(t => t.StartTime).ToList();
this.list.AddRange(orderedTones);
}
///
/// Prepares the midi tones.
///
/// The midi track.
/// The tones.
/// All tones.
/// If set to true [full length].
private void PrepareMidiTones(IMidiTrack midiTrack, MidiTones tones, MidiTones allTones, bool fullLength) {
Contract.Requires(midiTrack != null);
Contract.Requires(midiTrack.Events != null);
Contract.Requires(tones != null);
Contract.Requires(allTones != null);
var instrument = midiTrack.InstrumentNumber;
var channel = midiTrack.Channel;
foreach (var ev in midiTrack.Events) {
bool noteOn = false, noteOff = false;
VoiceNoteOn eventOn = null;
var eventClass = ev.GetType().ToString();
var eventType = Path.GetExtension(eventClass);
switch (eventType) {
case ".VoiceProgramChange": {
var programChange = (VoiceProgramChange)ev;
instrument = programChange.Number;
channel = programChange.Channel;
continue;
}
case ".VoiceNoteOn": {
eventOn = (VoiceNoteOn)ev;
noteOn = eventOn.Velocity > 0;
noteOff = eventOn.Velocity == 0;
this.currentStartTime = eventOn.StartTime;
this.currentNoteNumber = eventOn.Note;
channel = eventOn.Channel;
break;
}
case ".VoiceNoteOff": {
var levOff = (VoiceNoteOff)ev;
noteOff = true;
this.currentStartTime = levOff.StartTime;
this.currentNoteNumber = levOff.Note;
break;
}
default: {
if (!ev.IsMetaEvent) {
this.OtherEventList.Add(ev);
}
break;
}
}
if (noteOn) {
this.AddNoteOnToTones(midiTrack, tones, instrument, channel, eventOn);
}
// ReSharper disable once InvertIf
if (noteOff) {
var mt = this.CompleteCurrentNote(midiTrack, tones, fullLength);
if (mt == null) {
continue;
}
allTones.List.Add(mt);
tones.List.Remove(mt);
}
}
}
///
/// Completes the current note.
///
/// The midi track.
/// The tones.
/// If set to true [full length].
/// Returns value.
private IMidiTone CompleteCurrentNote(IMidiTrack midiTrack, MidiTones tones, bool fullLength) {
Contract.Requires(tones != null);
Contract.Requires(midiTrack != null);
var noteNumber = this.currentNoteNumber;
var toff = (from t in tones.List where t.NoteNumber == noteNumber select t).ToList();
if (!toff.Any()) {
return null;
}
var mt = toff.First();
if (mt == null) {
return null;
}
mt.CompleteDuration(this.currentStartTime, fullLength, midiTrack.BarDivision);
var lastReadTone = tones.List.LastOrDefault();
//// MidiTone lastCompleteTone = allTones.LastOrDefault();
if (lastReadTone != null) {
mt.SoundThrough = mt.StartTime < lastReadTone.StartTime;
}
return mt;
}
///
/// Adds the note on to tones.
///
/// The midi track.
/// The tones.
/// The instrument.
/// The channel.
/// The voice note-on.
private void AddNoteOnToTones(IMidiTrack midiTrack, MidiTones tones, byte instrument, MidiChannel channel, VoiceNoteOn eventOn) {
Contract.Requires(midiTrack != null);
Contract.Requires(eventOn != null);
Contract.Requires(tones != null);
var mt = new MidiTone(this.currentStartTime, this.currentNoteNumber, eventOn.Velocity, instrument, channel) {
BarNumberFrom = (int)Math.Floor((double)this.currentStartTime / midiTrack.BarDivision) + 1
};
if (mt.BarNumberFrom != this.lastBarNumber) {
this.lastBarNumber = mt.BarNumberFrom;
mt.FirstInBar = true;
}
tones.List.Add(mt);
}
#endregion
///
/// Complete Durations.
///
/// Full length.
/// Given division.
private void CompleteDurations(bool fullLength, int givenDivision) {
//// when note off events are missing
for (var i = 0; i < this.List.Count - 1; i++) {
var mt0 = this.List[i];
if (mt0 == null || mt0.Duration != 0) {
continue;
}
var mt1 = this.List[i + 1];
if (mt1 != null) {
mt0.CompleteDuration(mt1.StartTime, fullLength, givenDivision);
}
}
}
///
/// Add Collection.
///
/// Given tones.
private void AddRange(MidiTones givenTones) {
Contract.Requires(givenTones != null);
//// if (givenTone == null) { return false; }
this.list.AddRange(givenTones.List);
}
}
}